#include "TwitClient.h"

#include <stdlib.h>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netdb.h>

#define STDIN 0


const string TwitClient::USER_COMMAND_EXIT = "EXIT";
const string TwitClient::USER_COMMAND_FOLLOW = "FOLLOW";
const string TwitClient::USER_COMMAND_UNFOLLOW = "UNFOLLOW";
const string TwitClient::USER_COMMAND_DM = "DM";
const string TwitClient::USER_COMMAND_PA = "PA";
const string TwitClient::USER_COMMAND_TWIT = "TWIT";
const string TwitClient::USER_COMMAND_WHO = "WHO";

const int TwitClient::LEGAL_MESSAGE_SIZE = 140;
const unsigned int LEGAL_MACHINE_NAME_LEN = 30;

TwitClient::TwitClient(string myName,string serverAddress,short int serverPort){
	_userExited = false;
	_myName = myName;
	//server address initialization
	_serverAddress = new struct sockaddr_in;
	_serverAddress->sin_family = AF_INET;
	int transformationRes = inet_aton(serverAddress.c_str(), &(_serverAddress->sin_addr));

	//invalid address
	if(transformationRes != 0){
		_serverAddress->sin_port = htons(serverPort);
		memset(&(_serverAddress->sin_zero), '\0', 8);
		_mySocketFd = socket(PF_INET,SOCK_STREAM,0);
		FD_SET(STDIN,&_activeConnections);
		FD_SET(_mySocketFd,&_activeConnections);
		_isReadyToConnect = true;
	}
	else{
		_isReadyToConnect = false;
	}
}

int TwitClient::executeSimpleTwitCommand(string command,vector<string> arguments,string expectedResponse){
	Message msg;
	string msgStr = command;
	//start concatenating the command and its arguments
	for(unsigned int i=0;i<arguments.size();i++){
		msgStr = msgStr + " " + arguments[i];
	}
	//set message pointer
	msg.setMessageData(strdup(msgStr.c_str()));
	//send message
	unsigned int res = Protocol::sendMessage(_mySocketFd,msg.getMessageData(),msgStr.size()+1);
	//make sure the sending went well
	if(res != msgStr.size()+1){
		cout<<"ERROR: only "<<res<<" bytes were sent out of " << (msgStr.size()+1)<<endl;
	}

	return SUCCESS;
}

int TwitClient::connectToServer(){
	//starting the connection and returning FAILURE is sys call connect is unsuccessful
	if(connect(_mySocketFd, (struct sockaddr*)_serverAddress, sizeof(struct sockaddr)) != 0){
		return FAILURE;
	}

	Message msg;
	msg.setMessageData(strdup(_myName.c_str()));
	//send the server connections details
	unsigned int res = Protocol::sendMessage(_mySocketFd,msg.getMessageData(),_myName.size()+1);
	if(res != _myName.size()+1){
		cout<<"ERROR: only "<<res<<" bytes were sent out of " << (_myName.size()+1)<<endl;
	}
	//expect a confirmation message the client was proccessed by server
	Protocol::readMessage(_mySocketFd,msg.getMessage());
	string answer((char*)msg.getMessageData());

	//if something went wrong, return an error
	if(answer.compare(Protocol::WELCOME_MSG) != 0){
		return FAILURE;
	}

	return SUCCESS;
}

string TwitClient::validateMessage(string message){
	if(message.length() > (unsigned int)LEGAL_MESSAGE_SIZE){
		string result = message.substr(0,LEGAL_MESSAGE_SIZE);
		return result;
	}
	else{
		return message;
	}
}

int TwitClient::disconnectFromServer(){
	vector<string> arguments;
	return executeSimpleTwitCommand(Protocol::DISCONNECT_MSG,arguments,Protocol::BYE_BYE_MSG);
}

int TwitClient::followUser(string clientName){
	vector<string> arguments;
	arguments.push_back(clientName);
	return executeSimpleTwitCommand(Protocol::FOLLOW_MSG,arguments,Protocol::ACK_MSG);
}

int TwitClient::unfollowUser(string clientName){
	vector<string> arguments;
	arguments.push_back(clientName);
	return executeSimpleTwitCommand(Protocol::UNFOLLOW_MSG,arguments,Protocol::ACK_MSG);
}

int TwitClient::twit(string message){
	message = validateMessage(message);
	vector<string> arguments;
	arguments.push_back(message);
	return executeSimpleTwitCommand(Protocol::TWIT_MSG,arguments,Protocol::ACK_MSG);
}

int TwitClient::displayTwitMessage(string message){
	cout<<message<<endl;
	return SUCCESS;
}

int TwitClient::publicAnnouncement(string message){
	message = validateMessage(message);
	vector<string> arguments;
	arguments.push_back(message);
	return executeSimpleTwitCommand(Protocol::PA_MESSAGE_MSG,arguments,Protocol::ACK_MSG);
}

int TwitClient::directMessage(string machineID, string message){
	message = validateMessage(message);
	vector<string> arguments;
	//put directMessage at the agreed twit network format
	arguments.push_back(machineID +Protocol::MSG_SEPARATOR+ message);
	return executeSimpleTwitCommand(Protocol::DIRECT_MESSAGE_MSG,arguments,Protocol::ACK_MSG);
}

int TwitClient::who(){

	Message msg;

	msg.setMessageData(strdup(Protocol::WHO_MSG.c_str()));
	unsigned int res = Protocol::sendMessage(_mySocketFd,msg.getMessageData(),Protocol::WHO_MSG.size()+1);

	if(res != Protocol::WHO_MSG.size()+1){
		cout<<"ERROR: only "<<res<<" bytes were sent out of " << (Protocol::WHO_MSG.size()+1)<<endl;
	}

	string answer = "",whoMsg ="";

	//keep receiving until END_OF_LIST from server
	while(answer.compare(Protocol::END_OF_LIST_MSG) != 0){
		Protocol::readMessage(_mySocketFd,msg.getMessage());
		answer = string((char*)msg.getMessageData());
		whoMsg = whoMsg + answer + '\t';
	}
	//parse string to display
	whoMsg = whoMsg.substr(0,whoMsg.length()-1-Protocol::END_OF_LIST_MSG.length()-1);
	displayTwitMessage(whoMsg);
	return SUCCESS;
}

int TwitClient::receiveMessageFromServer(){
	Message msg;

	Protocol::readMessage(_mySocketFd,msg.getMessage());

	string answer((char*)msg.getMessageData()),userMessage = "";

	//Check for sudden disconnect from server
	if(answer.compare(Protocol::DISCONNECT_MSG) == 0){
		cout<<"The server had disconnected for an unknown reason,the client will shutdown"<<endl;
		_userExited = true;
	}
	userMessage = answer;
	//if user hadn't exited, show him the message
	if(!_userExited){
		return displayTwitMessage(userMessage);
	}
	else{
		return SUCCESS;
	}
}

void TwitClient::runClient() {
	fd_set readfds;
	while(!_userExited) {
		readfds = _activeConnections;
		if (select(_mySocketFd + 1, &readfds, NULL, NULL, NULL) == -1) {
			cout<<"System Call Error" << endl;
		}
		// run through the existing connections looking for data to read
		for (int i = 0; i <= _mySocketFd; i++) {
			if (FD_ISSET(i, &readfds)) { // we got one!!
				//There's a incoming message from the server
				if (i == _mySocketFd) {
					// handle incoming message
					receiveMessageFromServer();
				}
				//Handling input from STDIN:
				else if(i == STDIN)
				{
					processUserCommand();
				}
				//unknown connection request
				else
				{
					continue;
				}
			}
		}
	}
}

void TwitClient::processUserCommand(){
	string userLine = "",userCommand = "",userArgs="";
	getline(cin,userLine);
	int posOfFirstSpace = userLine.find(" ");
	if(posOfFirstSpace>-1){
		userCommand = userLine.substr(0,posOfFirstSpace);
		userArgs = userLine.substr(posOfFirstSpace+1,userLine.length()-1);
	}
	else{
		userCommand = userLine;
	}

	if(userCommand.compare(USER_COMMAND_FOLLOW) == 0){
		followUser(userArgs);
	}
	else if(userCommand.compare(USER_COMMAND_UNFOLLOW) == 0){
		unfollowUser(userArgs);
	}
	else if(userCommand.compare(USER_COMMAND_TWIT) == 0){
		twit(userArgs);
	}
	else if(userCommand.compare(USER_COMMAND_DM) == 0){
		string machineName = "",message = "";
		//parse the line according to twit network norms
		int pos = userArgs.find(Protocol::MSG_SEPARATOR);
		if(pos>-1){
			machineName = userArgs.substr(0,pos);
			message = userArgs.substr(pos+1,userArgs.length()-1);
			directMessage(machineName,message);
		}
		else{
			disconnectFromServer();
		}
	}
	else if(userCommand.compare(USER_COMMAND_PA) == 0){
		publicAnnouncement(userArgs);
	}
	else if(userCommand.compare(USER_COMMAND_WHO) == 0){
		who();
	}
	else if(userCommand.compare(USER_COMMAND_EXIT) == 0){
		_userExited = true;
		disconnectFromServer();
	}
	return;
}

bool TwitClient::isReadyToConnect(){
	return _isReadyToConnect;
}

//A helper function validated the desired machine name of the user
//The function return SUCCESS on success and FAILURE otherwise
int isMachineNameLegal(string name){
	//findding forbidden characters
	int tmpSep = name.find(Protocol::MSG_SEPARATOR);
	if(name.length() > LEGAL_MACHINE_NAME_LEN || tmpSep > -1){
		return FAILURE;
	}
	return SUCCESS;
}

int main(int argc, char **argv) {
	//validating usage
	if(argc != 4){
		cout<<"twitServer usage: <clientName> <server IP> <server port>"<<endl;
	}
	else{
		//connect to server
		if(isMachineNameLegal(argv[1]) == SUCCESS){
			TwitClient* tClient = new TwitClient(argv[1],argv[2],(unsigned short) strtoul(argv[3], NULL, 0));
			if(!(tClient->isReadyToConnect()) || tClient->connectToServer() != SUCCESS){
				cout << "Failed to connect to server"<<endl;
				exit(-1);
			}
			cout<<"Connected successfully"<<endl;
			tClient->runClient();
		}
		else{
			cout<<"Machine Name is Illegal" <<endl;
			exit(-1);
		}
		exit(-1);
	}
	return 0;
}




